﻿global using Devonline.Core;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;

namespace Devonline.Utils;

public static class ImageUtility
{
    /// <summary>
    /// 根据分辨率和格式重命名图片文件
    /// 格式说明: D: 目录名, R: 分辨率, F: 文件名, 其余部分只能是符号
    /// 格式示例: D/R/F -> 分辨率作为目录名后缀, 默认格式
    /// 格式示例: D/R_F -> 分辨率作为文件名前缀
    /// 格式示例: D/F_R -> 分辨率作为文件名后缀
    /// 格式示例: D_R_F -> 目录名, 分辨率都作为文件名前缀
    /// 格式示例: D(R)/F -> 分辨率作为目录名后缀
    /// </summary>
    /// <param name="imageFileName">图片文件名</param>
    /// <param name="outPath">输出目录</param>
    /// <param name="format">文件命名格式</param>
    /// <returns>输出文件名</returns>
    /// <exception cref="Exception"></exception>
    public static async Task<string> RenameImageAsync(string imageFileName, string? outPath = default, string? format = default)
    {
        ArgumentNullException.ThrowIfNull(imageFileName);
        using var image = await Image.LoadAsync(imageFileName);
        var extension = Path.GetExtension(imageFileName);
        //格式: R
        var resolution = $"{image.Width}x{image.Height}";
        //格式: D 
        var filePath = outPath ?? Path.GetDirectoryName(imageFileName);
        //格式: F
        var fileName = Path.GetFileNameWithoutExtension(imageFileName);
        if (fileName.Contains(resolution))
        {
            fileName = fileName.Replace(resolution, string.Empty);
        }

        format ??= "D/R/F";
        var indexDic = new Dictionary<char, int>();
        var index = format.IndexOf('D');
        if (index < 0)
        {
            index = int.MaxValue;
        }

        indexDic.Add('D', index);

        index = format.IndexOf('R');
        if (index < 0)
        {
            index = int.MaxValue;
        }

        indexDic.Add('R', index);

        index = format.IndexOf('F');
        if (index < 0)
        {
            index = int.MaxValue;
        }

        indexDic.Add('F', index);

        indexDic = indexDic.OrderByDescending(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
        var newFileName = format;
        foreach (var item in indexDic)
        {
            if (item.Value >= 0 && item.Value <= format.Length)
            {
                newFileName = newFileName[0..item.Value] + (item.Key switch
                {
                    'D' => filePath,
                    'R' => resolution,
                    'F' => fileName,
                    _ => string.Empty
                }) + newFileName[(item.Value + 1)..];
            }
        }

        newFileName += extension;
        filePath = Path.GetDirectoryName(newFileName);
        if (string.IsNullOrWhiteSpace(filePath))
        {
            throw new Exception($"The file path of {imageFileName} can not be null.");
        }

        if (!Directory.Exists(filePath))
        {
            Directory.CreateDirectory(filePath);
        }

        if (File.Exists(newFileName))
        {
            throw new Exception($"The file {newFileName} already exists.");
        }

        File.Move(imageFileName, newFileName);
        return newFileName;
    }

    /// <summary>
    /// 制作裁剪图, 不校验图片文件类型, 使用默认设置创建裁剪图
    /// </summary>
    /// <param name="fileName">文件名</param>
    /// <param name="size">裁剪尺寸, 使用此尺寸自适应, 即长款的最大值使用此尺寸</param>
    /// <param name="prefix">裁剪图文件前缀</param>
    /// <returns>输出文件名</returns>
    /// <exception cref="FileNotFoundException"></exception>
    public static async Task<string> CreateDefaultCropImageAsync(this string fileName, int size, string prefix = AppSettings.DEFAULT_IMAGE_CROP_PREFIX)
    {
        if (!File.Exists(fileName))
        {
            throw new FileNotFoundException("原文件不存在!", fileName);
        }

        using var fileStream = File.OpenRead(fileName);
        using var image = await Image.LoadAsync(fileStream);
        var cropImage = Path.Combine(Path.GetDirectoryName(fileName)!, prefix + Path.GetFileName(fileName));
        await image.CropAsync(size, size, cropImage);
        return cropImage;
    }
    /// <summary>
    /// 制作图片文件裁剪图, 不校验图片文件类型
    /// </summary>
    /// <param name="fileName">图片对象</param>
    /// <param name="width">最大宽度</param>
    /// <param name="height">最大高度</param>
    /// <param name="outFileName">裁剪后的输出图片路径</param>
    /// <exception cref="FileNotFoundException"></exception>
    public static async Task CropImageAsync(this string fileName, int width, int height, string outFileName)
    {
        if (!File.Exists(fileName))
        {
            throw new FileNotFoundException("文件不存在!", fileName);
        }

        using var fileStream = File.OpenRead(fileName);
        using var image = await Image.LoadAsync(fileStream);
        await image.CropAsync(width, height, outFileName);
    }
    /// <summary>
    /// 制作图片文件裁剪图, 当且仅当只有原图片尺寸大于裁剪图尺寸时才会生成裁剪图
    /// </summary>
    /// <param name="original">图片对象</param>
    /// <param name="width">最大宽度</param>
    /// <param name="height">最大高度</param>
    /// <param name="outFileName">裁剪后的输出图片路径</param>
    public static async Task CropAsync(this Image original, int width, int height, string outFileName)
    {
        try
        {
            var size = original.Size;
            var newSize = ResizeImage(original.Width, original.Height, width, height);
            if (size.Height > newSize.Height && size.Width > newSize.Width)
            {
                using var image = original.CloneAs<Rgba32>();
                image.Mutate(x => x.Resize(newSize));
                await image.SaveAsync(outFileName);
            }
        }
        finally
        {
            original.Dispose();
        }
    }

    /// <summary>
    /// 计算新尺寸
    /// </summary>
    /// <param name="width">原始宽度</param>
    /// <param name="height">原始高度</param>
    /// <param name="maxWidth">最大新宽度</param>
    /// <param name="maxHeight">最大新高度</param>
    /// <returns></returns>
    private static Size ResizeImage(int width, int height, int maxWidth, int maxHeight)
    {
        if (maxWidth <= 0)
        {
            maxWidth = width;
        }

        if (maxHeight <= 0)
        {
            maxHeight = height;
        }

        float MAX_WIDTH = maxWidth;
        float MAX_HEIGHT = maxHeight;
        var ASPECT_RATIO = MAX_WIDTH / MAX_HEIGHT;

        int newWidth, newHeight;
        float originalWidth = width;
        float originalHeight = height;

        if (originalWidth > MAX_WIDTH || originalHeight > MAX_HEIGHT)
        {
            float factor;
            if (originalWidth / originalHeight > ASPECT_RATIO)
            {
                factor = originalWidth / MAX_WIDTH;
                newWidth = Convert.ToInt32(originalWidth / factor);
                newHeight = Convert.ToInt32(originalHeight / factor);
            }
            else
            {
                factor = originalHeight / MAX_HEIGHT;
                newWidth = Convert.ToInt32(originalWidth / factor);
                newHeight = Convert.ToInt32(originalHeight / factor);
            }
        }
        else
        {
            newWidth = width;
            newHeight = height;
        }

        return new Size(newWidth, newHeight);
    }
}